using System;
using UnityEngine;

namespace DanielSig.Geometry
{
	public struct Coordinates64 : ISphericalCoordinates, I64Bit
	{
		#region type casts
		public static implicit operator Coordinates64(Coordinates32 original)
		{
			return new Coordinates64(original);
		}
		public static explicit operator Coordinates64(Vector3 vector)
		{
			return new Coordinates64(vector);
		}
		public static explicit operator Coordinates64(Quaternion quaternion)
		{
			return quaternion * new Coordinates64(0, 0, 1);
		}
		public static implicit operator Vector3(Coordinates64 coordinates)
		{
			return coordinates.vector;
		}
		public static implicit operator Quaternion(Coordinates64 coordinates)
		{
			return Quaternion.LookRotation(coordinates.vector);
		}
		#endregion
		#region operators
		public static Coordinates64 operator *(Quaternion quaternion, Coordinates64 coordinates)
		{
			return new Coordinates64(quaternion * coordinates.vector);
		}
		public static Coordinates64 operator *(Coordinates64 coordinates, float scalar)
		{
			Coordinates64 newCoordinates = new Coordinates64(coordinates);
			newCoordinates.distance *= scalar;
			return newCoordinates;
		}
		public static Coordinates64 operator /(Coordinates64 coordinates, float divisor)
		{
			Coordinates64 newCoordinates = new Coordinates64(coordinates);
			newCoordinates.distance /= divisor;
			return newCoordinates;
		}
		public static Coordinates64 operator +(Coordinates64 coordinates, Vector3 vector)
		{
			return new Coordinates64(coordinates.vector + vector);
		}
		public static Coordinates64 operator -(Coordinates64 coordinates, Vector3 vector)
		{
			return new Coordinates64(coordinates.vector - vector);
		}
		#endregion
		#region constructors
		internal Coordinates64(ulong data)
		{
			_data = data;
		}
		public Coordinates64(Vector3 vector)
		{
			_data = 0;
			this.vector = vector;
		}
		public Coordinates64(Coordinates32 original)
		{
			_data = 0;
			sphericalCoordinates = original.sphericalCoordinates;
		}
		public Coordinates64(Coordinates64 original)
		{
			_data = original._data;
		}
		public Coordinates64(float x, float y, float z)
				 : this(new Vector3(x, y, z))
		{
		}
		#endregion
		
		
		public static Coordinates64 Euler(Vector3 vector)
		{
			return (Coordinates64)Quaternion.Euler(vector);
		}
		public static Coordinates64 Euler(float x, float y, float z)
		{
			return (Coordinates64)Quaternion.Euler(x, y, z);
		}
		public override string ToString ()
		{
			return string.Format ("[Coordinates64: vector={0}, \nxAngle={1}, yAngle={2}, distance={3}]", vector, xAngle, yAngle, distance);
		}
		#region getters
		#region spherical coordinates getter and setter
		public Vector3 sphericalCoordinates
		{
			get
			{
				return new Vector3(xAngle, yAngle, distance);
			}
			set
			{
				_data = 0;
				_data += (ulong)Mathf.Round(Mathf.Repeat(value.x / 180F, 1F) * X_ANGLE_RESOLUTION) << (DIST_BITS + Y_ANGLE_BITS);
				_data += (ulong)Mathf.Round(Mathf.Repeat(value.y / 360F, 1F) * Y_ANGLE_RESOLUTION) << DIST_BITS;
				_data += (ulong)Mathf.Round(Mathf.Clamp01((float)value.z) * DIST_RESOLUTION);
			}
		}
		public Vector2 angles
		{
			get
			{
				return new Vector2(xAngle, yAngle);
			}
			set
			{
				_data = (_data << (X_ANGLE_BITS + Y_ANGLE_BITS)) >> (X_ANGLE_BITS + Y_ANGLE_BITS);
				_data += (ulong)Mathf.Round(Mathf.Repeat(value.x / 180F, 1F) * X_ANGLE_RESOLUTION) << (DIST_BITS + Y_ANGLE_BITS);
				_data += (ulong)Mathf.Round(Mathf.Repeat(value.y / 360F, 1F) * Y_ANGLE_RESOLUTION) << DIST_BITS;
			}
		}
		
		#endregion
		#region spherical coordinates getters
		public float xAngle
		{
			get
			{
				return ((_data >> (Y_ANGLE_BITS + DIST_BITS)) / X_ANGLE_RESOLUTION) * 180F;
			}
			set
			{
				_data = (_data << X_ANGLE_BITS) >> X_ANGLE_BITS;
				_data += (ulong)Mathf.Round(Mathf.Repeat(value / 180F, 1F) * X_ANGLE_RESOLUTION) << (DIST_BITS + Y_ANGLE_BITS);
			}
		}
		public float yAngle
		{
			get
			{
				return (((_data << X_ANGLE_BITS) >> (X_ANGLE_BITS + DIST_BITS)) / Y_ANGLE_RESOLUTION) * 360F;
			}
			set
			{
				_data -= (_data << X_ANGLE_BITS) >> (X_ANGLE_BITS + DIST_BITS);
				_data += (ulong)Mathf.Round(Mathf.Repeat(value / 360F, 1F) * Y_ANGLE_RESOLUTION) << DIST_BITS;
			}
		}
		public float distance
		{
			get
			{
				return (_data - ((_data >> DIST_BITS) << DIST_BITS)) / DIST_RESOLUTION;
			}
			set
			{
				_data = (_data >> DIST_BITS) << DIST_BITS;
				_data += (ulong)Mathf.Round(Mathf.Clamp01((float)value) * DIST_RESOLUTION);
			}
		}
		public Vector3 normal
		{
			get
			{
				return SphereMath.ToVector(xAngle, yAngle, 1F);
			}
			set
			{
				angles = (Vector2)SphereMath.ToAngles(value);
			}
		}
		public ulong rawData
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		#endregion
		#region vector getters
		public Vector3 vector
		{
			get
			{
				return SphereMath.ToVector(xAngle, yAngle, distance);
			}
			set
			{
				sphericalCoordinates = SphereMath.ToSphericalCoordinates(value);
			}
		}
		public float x
		{
			get
			{
				return SphereMath.GetVectorX(xAngle, yAngle, distance);
			}
			set
			{
				sphericalCoordinates = SphereMath.ToSphericalCoordinates(value, y, z);
			}
		}
		public float y
		{
			get
			{
				return SphereMath.GetVectorY(xAngle, yAngle, distance);
			}
			set
			{
				sphericalCoordinates = SphereMath.ToSphericalCoordinates(x, value, z);
			}
		}
		public float z
		{
			get
			{
				return SphereMath.GetVectorZ(xAngle, yAngle, distance);
			}
			set
			{
				sphericalCoordinates = SphereMath.ToSphericalCoordinates(x, y, value);
			}
		}
		#endregion
		#endregion
		private ulong _data;
		
		private const int X_ANGLE_BITS = 21;
		private const int Y_ANGLE_BITS = 22;
		private const int DIST_BITS = 21;
		
		private const float X_ANGLE_RESOLUTION = (1 << X_ANGLE_BITS) - 1;
		private const float Y_ANGLE_RESOLUTION = (1 << Y_ANGLE_BITS) - 1;
		private const float DIST_RESOLUTION = (1 << DIST_BITS) - 1;
	}
}